package IC.Symbols;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import IC.AST.ASTNode;

public class SymbolTable {
	private int itemsInDispatchTable = 0;
	private int itemsInInstanceTable = 0;
	private ASTNode astNode;
	private Map<String, Symbol> symbolsMap  = new LinkedHashMap<String, Symbol>();
	private List<SymbolTable> subTables = new ArrayList<SymbolTable>();
	private SymbolTableType symbolTableType;
	private SymbolTable parentSymbolTable;
	private String symbolTableName;
	private boolean isStaticScope;
	private boolean alreadySeenByLir;
	
	public SymbolTable(SymbolTableType symbolTableType, String tableName) {
		this.symbolTableType = symbolTableType;
		symbolTableName = tableName;
		isStaticScope = false;
		alreadySeenByLir = false;
		
        if (parentSymbolTable != null) {
        	parentSymbolTable.InsertSubTable(this);
        	isStaticScope = parentSymbolTable.isStaticScope;
        }

        if (symbolTableType == SymbolTableType.Class) {
            itemsInInstanceTable = 1;
            itemsInDispatchTable = 0;

            if (parentSymbolTable != null && parentSymbolTable.symbolTableType == SymbolTableType.Class) {
                itemsInInstanceTable = parentSymbolTable.getNumberOfItemsInInstanceTable();
                itemsInDispatchTable = parentSymbolTable.getNumberOfItemsInDispatchTable();
            }
        }
	}
	
	public void setStaticScope(){
		isStaticScope = true;
	}

	public SymbolTable getParentSymbolTable() {
		return parentSymbolTable;
	}

	public void setParentSymbolTable(SymbolTable parentSymbolTable)
	{
		// add parent only if there is not one already
		if (this.parentSymbolTable == null)
			parentSymbolTable.InsertSubTable(this);
		this.parentSymbolTable = parentSymbolTable;
		
	}

	public String getTableName() {
		return symbolTableName;
	}

	public Map<String, Symbol> getSymbolsMap() {
		return symbolsMap;
	}

	public Symbol InsertSymbol(Symbol symbol)  {
		Symbol previousSymbol = symbolsMap.put(symbol.getName(), symbol);
		if (previousSymbol != null)
			return previousSymbol;
		
        if (symbolTableType == SymbolTableType.Class) {
            if (symbol.getKind() == Kind.Field) {
                symbol.setSymbolDistance(itemsInInstanceTable);
                itemsInInstanceTable++;
            }
            if (symbol.getKind() == Kind.VirtualMethod) {
            	
                if ((parentSymbolTable != null) && (parentSymbolTable.getSymbolTableType() == SymbolTableType.Class)) {
                    Symbol parentSymbol = parentSymbolTable.FindSymbol(symbol.getName(), null, true);
                    if ((parentSymbol != null) && (parentSymbol.getKind() == Kind.VirtualMethod)) {
                        symbol.setSymbolDistance(parentSymbol.getSymbolDistance());
                        return null;
                    }
                }
                symbol.setSymbolDistance(itemsInDispatchTable++);
            }
        }
        return null;
    }
		
				
				
				
	

	public List<SymbolTable> getSubTables() {
		return subTables;
	}

	public void InsertSubTable(SymbolTable symbolTable) {
		subTables.add(symbolTable);
	}

	public Symbol FindSymbol(String symbolName, Boolean onlyStatic,
			boolean alsoSearchUp) {
		Symbol symbol = symbolsMap.get(symbolName);
		
		if (symbol == null) {
			Boolean isOnlyStatic = GetStaticStatus(onlyStatic);
			return (alsoSearchUp && (parentSymbolTable != null)) ? parentSymbolTable
					.FindSymbol(symbolName, isOnlyStatic, alsoSearchUp) : null;
		}

		if (onlyStatic == null)
			return symbol;
		if (onlyStatic && symbol.getKind().FindInStaticSemantic())
			return symbol;
		if (!onlyStatic && !symbol.getKind().FindInStaticSemantic())
			return symbol;
		
		
		return null;
	}

	private Boolean GetStaticStatus(Boolean onlyStatic) {
		Boolean isOnlyStatic = null;
		if (onlyStatic!= null)
			isOnlyStatic = (onlyStatic || isStaticScope) ? true : onlyStatic;
		else
			isOnlyStatic = isStaticScope ? true : null;
		
		// search static fields until reaching the class scope
		if (isOnlyStatic == null){
			if (symbolTableType == SymbolTableType.Class)
				isOnlyStatic = false;
		}
		return isOnlyStatic;
	}

	@Override
	public String toString() {
		StringBuilder resultString = new StringBuilder();
		
		String headerString = String.format("\n%s Symbol Table", symbolTableType.toString());
		
		if (symbolTableType.PrintContainer())
			headerString = String.format("%s ( located in %s )" , headerString, parentSymbolTable.getTableName());
		else
			headerString = String.format("%s: %s" , headerString, symbolTableName);
		
		resultString.append(headerString + "\n");
		
		StringBuilder tableContent = new StringBuilder();
		for (Symbol symbol : symbolsMap.values())
			if (symbol.getKind().IncludeInSymbolsPrint())
				tableContent.append(String.format("\t%s\n", symbol.toString()));
		
		resultString.append(tableContent);
		
		if (subTables.size() > 0){
			StringBuilder subTablesName = new StringBuilder();
			StringBuilder subTablesContent = new StringBuilder();
			for(SymbolTable symbolTable : subTables){
				subTablesName.append(symbolTable.getTableName() + "\t");
				subTablesContent.append(symbolTable.toString() + "\n");
			}
			
			resultString.append("Children tables: " + subTablesName.toString().trim().replace("\t", ", ")+'\n');
			resultString.append(subTablesContent);
		}
		
		return resultString.toString();
	}

    public ASTNode getAstNode() {
        return astNode;
    }
    
    public boolean seenByLir() {
        return alreadySeenByLir;
    }
    
    public void setAstNode(ASTNode newAstNode) {
        astNode = newAstNode;
    }

    public void setSeenByLir() {
        alreadySeenByLir = true;;
    }

    public int getNumberOfItemsInDispatchTable() {
        return itemsInDispatchTable;
    }
    
    public int getNumberOfItemsInInstanceTable() {
        return itemsInInstanceTable;
    }
    
    public SymbolTableType getSymbolTableType(){
    	return symbolTableType;
    }
	
}
